package lime2.ca.treeplain;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import lime2.LimeServerID;
import lime2.Location;
import lime2.ca.CastInfo;

/**
 * Mesh maintains a collection of
 * 
 * @author Xing Jiankuan
 * 
 */
public class Mesh {

	private static final int MAXSERVERS = 100;
	private static final long INF = Long.MAX_VALUE; //not connect
	private static final long SELFDELAY = 0L;
	
	HashMap<LimeServerID, Integer> serversMap;
	
	Graph graph = null; // cached graph
	/**
	 * delay among servers
	 */
	long delayMatrix[][];
	
	/**
	 * the maximal index for servers
	 */
	int serversNum;

	/**
	 * constructor
	 * 
	 * @author Xing Jiankuan
	 */
	public Mesh() {
		serversMap = new HashMap<LimeServerID, Integer>();
		delayMatrix = new long[MAXSERVERS][MAXSERVERS];
		for (int i = 0; i < MAXSERVERS; i++) {
			for (int j = 0; j < MAXSERVERS; j++) {
				if (i == j)
					delayMatrix[i][j] = SELFDELAY;
				else
					delayMatrix[i][j] = INF;
			}
		}
		serversNum = 0;
		serversMap.put(Location.HERE.getID(), serversNum++);
	}

	/**
	 * Add a server message to mesh
	 * @param infoMsg
	 * @author Xing Jiankuan
	 */
	public synchronized void addServer(MeshInfoMsg infoMsg) {
		if (!serversMap.containsKey(infoMsg.src)) {
			serversMap.put(infoMsg.src, serversNum);
			serversNum++;
		}
		
		if(infoMsg.items != null){
		
			int srcIndex = serversMap.get(infoMsg.src);
				
			for (int i = 0; i < infoMsg.items.length; i++) {
				if (!serversMap.containsKey(infoMsg.items[i].dest)) {
					serversMap.put(infoMsg.items[i].dest, serversNum);
					serversNum++;
				}
			}
			for (int i = 0; i < serversNum; i++) {
				int destIndex = serversMap.get(infoMsg.items[i].dest);
				delayMatrix[srcIndex][destIndex] = serversMap
						.get(infoMsg.items[i].delay);
			}
		}
		graph = null; //clear cached graph
	}

	/**
	 * Remove server information from mesh
	 * @param server
	 * @author Xing Jiankuan
	 */

	public void removeServer(LimeServerID server) {
		if (serversMap.containsKey(server)) {
			int srcIndex = serversMap.get(server);
			for (int i = 0; i < serversNum; i++) {
				if (i != srcIndex) {
					delayMatrix[srcIndex][i] = INF;
					delayMatrix[i][srcIndex] = INF;
				}
			}
		}
		graph = null; //clear cached graph
	}

	/**
	 * Update a server-to-server delay
	 * @param src
	 * @param dest
	 * @param delay
	 * @author Xing Jiankuan
	 */
	public synchronized void updateMeshItem(LimeServerID src, LimeServerID dest, long delay) {
		if (serversMap.containsKey(src) && serversMap.containsKey(dest)) {
			int srcIndex = serversMap.get(src);
			int destIndex = serversMap.get(dest);
			delayMatrix[srcIndex][destIndex] = delay;
			graph = null; //clear cached graph
		}
	}
	
	/**
	 * get the cast tree
	 */
	private synchronized CastTree getCastTree() {
		if (graph == null) {
			graph = new Graph(this);
		}
		return graph.getMiniSpanningTree();
	}
	
	/**
	 * get the cat tree to eliminate all unnecessary hosts to 
	 * reach specified targets
	 * @param targets
	 * @return
	 * @author Xing Jiankuan
	 */
	private synchronized CastTree getCastTree(LimeServerID[] targets) {
		if (graph == null) {
			graph = new Graph(this);
		}
		return graph.getMiniSpanningTree(targets);
	}

	/**
	 * Indicate whether the mesh is prepared
	 * 
	 * @return
	 * @author Xing Jiankuan
	 */
	public boolean isPrepared() {
		return serversNum > 0;
	}

	/**
	 * Judge whether the specified record exists
	 * 
	 * @param record
	 * @return
	 * @author Xing Jiankuan
	 */
	public boolean isExist(LimeServerID id) {
		return serversMap.containsKey(id);
	}

	private static final Mesh meshInstance = new Mesh();

	public static Mesh getInstance() {
//		boolean exist = false;
//		while (!exist) { // wait for mesh's construction
//			if (meshInstance.isPrepared())
//				exist = true;
//		}
//		System.out.println("%%%%%%%%%%%%%The server number of instance is " + meshInstance.serversNum);
		return meshInstance;
	}

	/**
	 * Create a cast information for the given hosts. There are two possible cases:
	 * If some specified hosts are not in the mesh, they will be just ignored;
	 * If some hosts are unreachable unless some other hosts are added into the cast
	 * tree, the necessary supplementary is taken, which leads to the hosts in the
	 * cast tree is more than the specified ones.
	 * 
	 * @param hosts
	 * @return
	 * @author Xing Jiankuan
	 */
	public synchronized CastInfo createCastInfo(LimeServerID[] hosts) {
		CastTree tree = getCastTree(hosts);
		TreeCastInfo castInfos = new TreeCastInfo(tree);
		return castInfos;
	}

	/**
	 * Create a cast information for all the hosts in the mesh
	 * 
	 * @return
	 * @author Xing Jiankuan
	 */
	public synchronized CastInfo createCastInfoWithAll() {
		CastTree tree = getCastTree();
		TreeCastInfo castInfos = new TreeCastInfo(tree);
		return castInfos;
	}

	/**
	 * Update the delays from one given source to others
	 * 
	 * @param src
	 * @param items
	 * @author Xing Jiankuan
	 */
	public synchronized void updateRecord(LimeServerID src, MeshInfoItem[] items) {
		for (int i = 0; i < items.length; i++) {
			updateMeshItem(src, items[i].dest, items[i].delay);
		}
		
		if(items.length > 0)
			graph = null; //clear cached graph
	}

	public MeshInfoItem[] getSelfMeshInfos() {
		LimeServerID localServer = Location.HERE.getID();
		if (serversMap.containsKey(localServer)) {
			int srcIndex = serversMap.get(localServer);
			MeshInfoItem[] ret = new MeshInfoItem[serversNum];
			Set entrySet = serversMap.entrySet();
			for (Iterator iter = entrySet.iterator(); iter.hasNext();) {
				Map.Entry entry = (Map.Entry) iter.next();
				LimeServerID dest = (LimeServerID) entry.getKey();
				int index = (Integer) entry.getValue();
				ret[index] = new MeshInfoItem(dest,
						delayMatrix[srcIndex][index]);
			}
			return ret;
		}
		return null;
	}

	/**
	 * Clear all the mesh information
	 * 
	 * @author Xing Jiankuan
	 */
	public void clear() {

		for (int i = 0; i < serversNum; i++) {
			for (int j = 0; j < serversNum; j++) {
				if (i == j)
					delayMatrix[i][j] = SELFDELAY;
				else
					delayMatrix[i][j] = INF;
			}
		}
		serversMap.clear();
	}
	
	public String toString() {
		StringBuffer sb = new StringBuffer();
		//first output serversMap
		Iterator<LimeServerID> itKeys = serversMap.keySet().iterator();
		for(; itKeys.hasNext(); ) {
			LimeServerID server = itKeys.next();
			sb.append(server.toString() + "@" + serversMap.get(server).toString() + ";");
		}
		sb.append("\n");
		
		// then show the matrix
		sb.append("\t");
		int numServers = serversMap.size();
		for(int i = 0; i < numServers; i++) {
			sb.append(i);
			sb.append("\t");
		}
		sb.append("\n");
		for(int i = 0; i < numServers; i++) {
			sb.append(i);
			sb.append("\t");
			for(int j = 0; j < numServers; j++) {
				if(delayMatrix[i][j] == Long.MAX_VALUE)
					sb.append("INF");
				else
					sb.append(delayMatrix[i][j]);
				sb.append("\t");
			}
			sb.append("\n");
		}
		return sb.toString();
	}
}
